/*
	Copyright (c) 2004-2006, The Dojo Foundation
	All Rights Reserved.

	Licensed under the Academic Free License version 2.1 or above OR the
	modified BSD license. For more information on Dojo licensing, see:

		http://dojotoolkit.org/community/licensing.shtml
*/

dojo.provide("dojo.charting.Series");
dojo.require("dojo.lang.common");
dojo.require("dojo.charting.Plotters");

dojo.charting.Series = function(/* object? */kwArgs){
	//	summary
	//	Create an instance of data series for plotting.
	var args = kwArgs || { length:1 };
	this.dataSource = args.dataSource || null;
	this.bindings = { };
	this.color = args.color;
	this.label = args.label;

	if(args.bindings){
		for(var p in args.bindings){
			this.addBinding(p, args.bindings[p]);
		}
	}
};

dojo.extend(dojo.charting.Series, {
	bind:function(/* dojo.collections.Store */src, /* object */bindings){
		//	summary
		//	Bind this series to src, with bindings.
		this.dataSource = src;
		this.bindings = bindings;
	},
	addBinding:function(/* string */name, /* string */binding){
		//	summary
		//	Bind to field "binding" using "name".
		this.bindings[name] = binding;
	},
	evaluate:function(/* object? */kwArgs){
		//	summary
		//	Evaluate all bindings and return an array of objects describing the bind.
		var ret = [];
		var a = this.dataSource.getData();
		var l = a.length;
		var start = 0;
		var end = l;
		
		/*	Allow for ranges.  Can be done in one of two ways:
		 *	1. { from, to } as 0-based indices
		 *	2. { length } as num of data points to get; a negative
		 *		value will start from the end of the data set.
		 *	No kwArg object means the full data set will be evaluated
		 *		and returned.
		 */
		if(kwArgs){
			if(kwArgs.from){ 
				start = Math.max(kwArgs.from,0);
				if(kwArgs.to){ 
					end = Math.min(kwArgs.to, end);
				}
			}
			else if(kwArgs.length){
				if(kwArgs.length < 0){
					//	length points from end
					start = Math.max((end + length),0);
				} else {
					end = Math.min((start + length), end);
				}
			}
		}

		for(var i=start; i<end; i++){
			var o = { src: a[i], series: this };
			for(var p in this.bindings){
				o[p] = this.dataSource.getField(a[i], this.bindings[p]);
			}
			ret.push(o);
		}

		//	sort by the x axis, if available.
		if(typeof(ret[0].x) != "undefined"){
			ret.sort(function(a,b){
				if(a.x > b.x) return 1;
				if(a.x < b.x) return -1;
				return 0;
			});
		}
		return ret;	//	array
	},

	//	trends
	trends:{
		createRange: function(/* array */values, /* int */len){
			//	summary
			//	Creates the data range used for all trends.
			var idx = values.length-1;
			var length = (len||values.length);
			return { "index": idx, "length": length, "start":Math.max(idx-length,0) };	//	object
		},

		mean: function(/* array */values, /* int */len){
			//	summary
			//	Returns the mean or average over the set of values.
			var range = this.createRange(values, len);
			if(range.index<0){ return 0; }
			var total = 0;
			var count = 0;
			for(var i=range.index; i>=range.start; i--){
				total += values[i].y; 
				count++;
			}
			total /= Math.max(count,1);
			return total;	//	float
		},

		variance: function(/* array */values,/* int */len){
			//	summary
			//	Returns the variance of the set of values.
			var range = this.createRange(values,len);
			if(range.index < 0){ return 0; }
			var total = 0;
			var square = 0;
			var count = 0;
			for(var i=range.index; i>=range.start; i--){
				total += values[i].y;
				square += Math.pow(values[i].y, 2);
				count++;
			}
			return (square/count)-Math.pow(total/count,2);	//	float
		},

		standardDeviation: function(/* array */values, /* int */len){
			//	summary
			//	Returns the standard deviation of the set of values.
			return Math.sqrt(this.getVariance(values, len));	//	float
		},

		max: function(/* array */values, /* int */len){
			//	summary
			//	Returns the max number in the set of values.
			var range = this.createRange(values, len);
			if(range.index < 0){ return 0; }
			var max = Number.MIN_VALUE;
			for (var i=range.index; i>=range.start; i--){
				max = Math.max(values[i].y,max);
			}
			return max;	//	float
		},

		min: function(/* array */values, /* int */len){
			//	summary
			//	Returns the lowest number in the set of values.
			var range=this.createRange(values, len);
			if(range.index < 0){ return 0; }
			var min = Number.MAX_VALUE;
			for(var i=range.index; i>=range.start; i--){
				min = Math.min(values[i].y, min);
			}
			return min;	//	float
		},

		median: function(/* array */values, /* int */len){
			//	summary
			//	Returns the median in the set of values (number closest to the middle of a sorted set).
			var range = this.createRange(values, len);
			if(range.index<0){ return 0; }
			var a = [];
			for (var i=range.index; i>=range.start; i--){
				var b=false;
				for(var j=0; j<a.length; j++){
					if(values[i].y == a[j]){
						b = true;
						break;
					}
				}
				if(!b){ 
					a.push(values[i].y); 
				}
			}
			a.sort();
			if(a.length > 0){ 
				return a[Math.ceil(a.length / 2)]; 	//	float
			}
			return 0;	//	float
		},

		mode: function(/* array */values, /* int */len){
			//	summary
			//	Returns the mode in the set of values
			var range=this.createRange(values, len);
			if(range.index<0){ return 0; }
			var o = {};
			var ret = 0
			var median = Number.MIN_VALUE;
			for(var i=range.index; i>=range.start; i--){
				if (!o[values[i].y]){
					o[values[i].y] = 1;
				} else { 
					o[values[i].y]++;
				}
			}
			for(var p in o){
				if(median < o[p]){ 
					median = o[p]; 
					ret=p; 
				}
			}
			return ret;
		}
	}
});
